package BNS.plumbing;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintStream;

import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;

import BNS.graphs.GraphPack;


/**
 * This class takes cares of reading and writing BNSGameControllers to a
 * library. A FileChooser for this purpose will be initialized using the PREP
 * environment property. If no PREP property exists it will surely start in some
 * inconvenient place, but wherever the user navigates it to will be remembered
 * the next time something is read or written.
 * 
 * This class also takes care of writing session files (output from the
 * SessionHost that record data relevant to one entire run of the server), and
 * exp files (output from the various GameControllers meant to record the
 * activity of single games). These files will be stored in the directory named
 * by the ARCH environment property.
 */
public class Library {

	private static JFileChooser gameFC,gpackFC,logFC,anyFC;

	private static int lastExpNum,lastSessionNum;

	private static File archive=null,libDir;

	private static String gameLogName;

	private static String sessionLogName;

	private Library() {} // unused

	/**
	 * This method must be called before using any of the FileChooser methods in
	 * the Library. (There is no constructor.) It initializes a FileChooser for
	 * the storage and retrieval of games.
	 */
	public static void setPrep(File prep) {
		if(archive!=null) return; // guarantee Library opened only once
		libDir=prep;
		System.err.println("BNS Library started");
		System.err.println("PREP= "+prep);

		gameFC=new JFileChooser(libDir);
		gameFC.setFileFilter(new SuffixFileFilter("game"));
		gameFC.setFileSelectionMode(JFileChooser.FILES_ONLY);

		gpackFC=new JFileChooser(libDir);
		gpackFC.setFileFilter(new SuffixFileFilter("gpack"));
		gpackFC.setFileSelectionMode(JFileChooser.FILES_ONLY);

		anyFC=new JFileChooser(libDir);
		anyFC.setFileSelectionMode(JFileChooser.FILES_ONLY);
	}

	/**
	 * This method must be called before using the getSessionLog or getGameLog()
	 * methods. It accepts a directory, which will be used for storing session
	 * files and exp files.
	 */
	public static void setArch(File arch) {
		archive=arch;
		logFC=new JFileChooser(archive);
		logFC.setFileSelectionMode(JFileChooser.FILES_ONLY);
	}

	/*
	 * These lines were ripped out of the old version of init(): lastExpNum=
	 * fileScan(archive,"exp"); System.err.println("last experiment number used
	 * was "+ lastExpNum); lastSessionNum= fileScan(archive,"session");
	 * System.err.println("last session number used was "+ lastSessionNum);
	 */

	/**
	 * Examines the files in a directory to find the highest-numbered file that
	 * starts with a certain prefix. This is part of a scheme to create files in
	 * an orderly sequence that do not overwrite one another.
	 */
	private static int fileScan(File archive,String prefix) {
		String[] filenames=archive.list();
		int lastUsedNum=0;
		for(int i=0;i<filenames.length;i++) {
			// System.err.println("file list has "+ filenames[i]);
			if(filenames[i].startsWith(prefix)) {
				String seq=filenames[i].substring(prefix.length());
				int dash=seq.indexOf('-');
				if(dash>0) seq=seq.substring(0,dash);
				int used=Integer.parseInt(seq);
				if(used>lastUsedNum) lastUsedNum=used;
			}
		}
		return lastUsedNum;
	}

	/**
	 * Will create a file guaranteed to be new, to have the prefix "exp", and to
	 * have a higher suffix number than any other file created by this Library in
	 * its ARCHive directory.
	 */
	public static PrintStream newGameLog() {
		return newGameLog(null);
	}

	/**
	 * Will create a file guaranteed to be new, to have the prefix "exp", followed
	 * by an integer bigger than any other file created by this Library in its
	 * ARCHive directory, followed by the String in the argument.
	 */
	public static PrintStream newGameLog(File source) {
		lastExpNum=fileScan(archive,sessionLogName+"_exp");
		gameLogName=sessionLogName+"_exp"+(++lastExpNum);
		if(source!=null) {
			String src=source.getName();
			if(src.endsWith(".game")) src=src.substring(0,src.lastIndexOf('.'));
			gameLogName+="-"+src;
		}
		gameLogName+=".data";
		return newLog(gameLogName);
	}

	/** Returns the name of the file last created by newGameLog(). Be careful. */
	public static String getGameLogName() {
		return gameLogName;
	}

	/**
	 * Will create a file guaranteed to be new, to have the prefix "session", and
	 * to have a higher suffix number than any other file created by this Library
	 * in its ARCHive directory.
	 */
	public static PrintStream newSessionLog(String sessionName) {
		sessionLogName=sessionName;
		lastSessionNum=fileScan(archive,sessionName+"_session");
		return newLog(sessionName+"_session"+(++lastSessionNum));
	}

	private static PrintStream newLog(String name) {
		File log=new File(archive,name);
		try {
			if(!log.createNewFile()) throw new RuntimeException("cannot createNewFile "+log);
		} catch(IOException e) {
			e.printStackTrace();
		}
		try {
			return new PrintStream(log);
		} catch(FileNotFoundException e) {}
		return null;
	}

	/** Used just as a simple test exercise for other methods in this class. */
	public static void main(String[] args) {
		Library.setPrep(new File("./"));
		Library.setArch(new File("./"));
		Library.newGameLog().println("this is the game log");
		Library.newSessionLog("Test").println("this is the session log; "+"it refers to "+Library.getGameLogName());
	}

	/** Returns a previously built game that was stored in the PREP library. */
	public static synchronized BNSGameController getGame(JFrame mother) {
		BNSGameController gc=castGame(getFile(gameFC,mother));
		if(gc!=null) gc.setSource(gameFC.getSelectedFile());
		return gc;
	}

	/** Returns a previously built game that was stored in the File file. */
	public static synchronized BNSGameController getGame(File file) {
		BNSGameController gc=castGame(getFile(file));
		if(gc!=null) gc.setSource(file);
		return gc;
	}

	protected static synchronized BNSGameController castGame(Object ob) {
		BNSGameController gc=null;
		try {
			gc=(BNSGameController)ob;
		} catch(Exception ex) {
			JOptionPane.showMessageDialog(null,"You may be trying to load a game that was created \n"+"with a previous build of this software. Recreate it."+"\n"+ex.getMessage(),"alert",
					JOptionPane.ERROR_MESSAGE);
			System.err.println("castGame failed");
		}
		return gc;
	}

	/** obtains the name of a GraphPack file from the user */
	public static synchronized GraphPack getPack(File file) {
		return (GraphPack)getFile(file);
	}

	private static synchronized java.io.Serializable getFile(File file) {
		java.io.Serializable thing=null;
		try {
			FileInputStream fs=new FileInputStream(file);
			ObjectInputStream os=new ObjectInputStream(fs);
			thing=(java.io.Serializable)os.readObject();
			os.close();
		} catch(Exception ex) {
			System.err.println("problem: "+ex);
		}
		return thing;
	}

	/**
	 * obtains a GraphPack file from the user via a FileChooser.
	 * 
	 * @param mother
	 *          the JFrame that will be used to position the FileChooser panel.
	 */
	public static synchronized GraphPack getPack(JFrame mother) {
		return (GraphPack)getFile(gpackFC,mother);
	}

	/** obtains the name of a file of an arbitrary type from the user. */
	public static synchronized File getLog(JFrame mother) {
		File file=null;
		int returnVal=logFC.showOpenDialog(mother);
		if(returnVal==JFileChooser.APPROVE_OPTION) {
			try {
				file=logFC.getSelectedFile();
			} catch(Exception ex) {
				System.err.println("problem: "+ex);
			}
		}
		return file;
	}

	/**
	 * Retrieves an Object from some file that is selected by the user through a
	 * FileChooser operation. The FileChooser provided will have an altered
	 * state... namely the getSelectedFile() method will reflect the file from
	 * which the object was read.
	 */
	private static synchronized java.io.Serializable getFile(JFileChooser fc,JFrame mother) {
		java.io.Serializable thing=null;
		int returnVal=fc.showOpenDialog(mother);
		if(returnVal==JFileChooser.APPROVE_OPTION) {
			try {
				File file=fc.getSelectedFile();
				FileInputStream fs=new FileInputStream(file);
				ObjectInputStream os=new ObjectInputStream(fs);
				thing=(java.io.Serializable)os.readObject();
				os.close();
			} catch(Exception ex) {
				System.err.println("problem: "+ex);
			}
		}
		return thing;
	}

	/**
	 * Stores a game anywhere in the local file system the user selects. The
	 * FileChooser GUI is first initialized to point to the Library's PREP
	 * directory, but will subsequently point to the last directory in which
	 * something was stored.
	 */
	public static boolean putGame(JFrame mother,BNSGameController game) {
		return putObject(gameFC,mother,game);
	}

	/** Stores a game at the file in the local file system specified. */
	public static boolean putGame(File fname,BNSGameController game) {
		File fullName=new File(libDir,fname.toString());
		return putObject(gameFC,fullName,game);
	}

	/**
	 * Stores a GraphPack anywhere in the local file system the user selects. The
	 * FileChooser GUI is first initialized to point to the Library's PREP
	 * directory, but will subsequently point to the last directory in which
	 * something was stored.
	 */
	public static boolean putPack(JFrame mother,BNS.graphs.GraphPack gp) {
		return putObject(gpackFC,mother,gp);
	}

	/**
	 * Stores a GraphPack in the directory defined by PREP. The file name is
	 * initialized to the 3rd argument, but may be changed by hand.
	 */
	public static boolean putPack(JFrame mother,BNS.graphs.GraphPack gp,File file) {
		gpackFC.setSelectedFile(file);
		return putObject(gpackFC,mother,gp);
	}

	private static boolean putObject(JFileChooser fc,JFrame mother,java.io.Serializable thing) {
		int returnVal=fc.showSaveDialog(mother);
		if(returnVal==JFileChooser.APPROVE_OPTION) {
			File file=fc.getSelectedFile();
			file=((SuffixFileFilter)fc.getFileFilter()).comply(file);
			return putObject(file,thing);
		}
		System.err.println("user canceled save");
		return false;
	}

	private static boolean putObject(JFileChooser fc,File file,java.io.Serializable thing) {
		file=((SuffixFileFilter)fc.getFileFilter()).comply(file);
		return putObject(file,thing);
	}

	private static synchronized boolean putObject(File file,java.io.Serializable thing) {
		try {
			FileOutputStream fs=new FileOutputStream(file);
			ObjectOutputStream os=new ObjectOutputStream(fs);
			os.writeObject(thing);
			os.close();
			// System.err.println("saved object in "+ file);
			return true;
		} catch(Exception ex) {
			System.err.println("problem: "+ex);
		}
		System.err.println("failed to save object");
		return false;
	}

	public static File getSelectedFile() {
		anyFC.setFileFilter(new SuffixFileFilter(null));
		anyFC.setFileSelectionMode(JFileChooser.FILES_ONLY);
		int returnVal=anyFC.showOpenDialog(null);
		if(returnVal==JFileChooser.APPROVE_OPTION) { return anyFC.getSelectedFile(); }
		return null;
	}


	/** used to filter files that end with a given suffix */
	static class SuffixFileFilter extends javax.swing.filechooser.FileFilter {
		private String suffix;

		public SuffixFileFilter(String suf) {
			suffix=suf;
		}

		public boolean accept(File f) {
			if(f.isDirectory()) return true;
			if(suffix==null) return true;
			String ext=null;
			String s=f.getName();
			int i=s.lastIndexOf('.');
			if(i>0&&i<s.length()-1) ext=s.substring(i+1).toLowerCase();
			if(ext!=null&&ext.equals(suffix)) return true;
			return false;
		}

		public String getDescription() {
			if(suffix==null) return "any files";
			if(suffix.equals("gpack")) return "just GraphPack files";
			if(suffix.equals("game")) return "just BNSGame files";
			return "just "+suffix+" files";
		}

		/** appends the required suffix to the filename if it ain't already there. */
		public File comply(File ff) {
			if(accept(ff)) return ff;
			return new File(ff.getParentFile(),ff.getName()+"."+suffix);
		}
	}

} // EOC Library
